// ==UserScript==
// @name 咽沙学习小助手测试版V0.9 公开测试版
// @namespace http://tampermonkey.net/
// @version 0.9
// @description 创建一个明显可见且可拖动的聊天窗口,集成 OpenAI API 互动
// @author Your Name
// @match *://*/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
const API_SECRET_KEY = "sk-zkix22izlS26fVKhfAtHnRa2A8rT9xZyQqAu6Sf8NfiXPPtdYEYN";
const BASE_URL = "https://model-bridge.okeeper.com/v1/";
// 添加 CSS 样式 - 确保样式定义正确
GM_addStyle(`
/* 确保所有相关元素都有合适的样式 */
#button-container, .option-button, #chat-window, #auto-answer-chat-window, #input-container, .copy-button {
font-family: "STKaiti", "华文楷体", "Kaiti", serif;
}
#button-container {
position: fixed; /* 固定定位 */
top: 100px; /* 调整为距离顶部100px */
left: 0; /* 左边 */
z-index: 10000; /* 确保始终位于最上层 */
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景色 */
border-bottom: 1px solid black; /* 底部边界线 */
width: 100%; /* 宽度为整个屏幕 */
text-align: center;
}
.option-button {
display: none; /* 默认隐藏子按钮 */
margin-top: 5px;
padding: 10px;
cursor: pointer;
border: 1px solid #ccc;
background-color: white;
transition: all 0.3s ease;
}
.option-button:hover {
background-color: #e6e6fa;
}
#parent-button {
cursor: pointer;
padding: 10px;
text-align: center;
background-color: #e6e6fa;
border: 1px solid #ccc;
}
#chat-window, #auto-answer-chat-window {
position: fixed;
bottom: 0;
right: 0;
width: 400px;
height: 600px;
background-color: white;
border: 1px solid black;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
overflow: hidden;
display: none;
z-index: 10001; /* 确保聊天窗口在最上方 */
}
.chat-content {
height: 80%;
overflow-y: auto;
padding: 10px;
border-bottom: 1px solid #ccc;
}
#input-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.copy-button {
padding: 5px 10px;
cursor: pointer;
border: 1px solid #ccc;
background-color: white;
transition: all 0.3s ease;
}
.copy-button:hover {
background-color: #e6e6fa;
}
`);
function createButtonContainer() {
const buttonContainer = document.createElement('div');
buttonContainer.id = 'button-container';
return buttonContainer;
}
function createParentButton(buttonContainer) {
const parentButton = document.createElement('button');
parentButton.id = 'parent-button';
parentButton.textContent = '咽沙学习小助手测试版 V0.9';
buttonContainer.appendChild(parentButton);
return parentButton;
}
function createOptionButtons(buttonContainer, chatWindowId) {
for (let i = 0; i < 5; i++) {
const optionButton = document.createElement('button');
optionButton.className = 'option-button';
if (i === 4) { // 对话GPT按钮
optionButton.textContent = '-对话GPT 【本按钮可切换窗口显示、隐藏】';
optionButton.onclick = () => toggleChatWindowVisibility(chatWindowId);
} else if (i === 1) { // 获取页面自动解答按钮
optionButton.textContent = '-获取页面自动解答';
optionButton.onclick = () => toggleChatWindowVisibility('auto-answer-chat-window');
} else {
optionButton.onclick = () => parentButton.click(); // 收起子容器
}
buttonContainer.appendChild(optionButton);
}
}
function toggleChatWindowVisibility(chatWindowId) {
const chatWindow = document.getElementById(chatWindowId);
chatWindow.style.display = chatWindow.style.display === 'none' ? 'block' : 'none';
}
// 改进获取页面文本逻辑,只提取可选中的文本节点
function getPageText() {
let allText = '';
const texts = [];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.parentElement.tagName !== 'SCRIPT' && node.parentElement.tagName !== 'STYLE') {
texts.push(node.nodeValue.trim());
}
}
return texts.filter(text => text).join('\n'); // 过滤掉空字符串并连接成一个整体
}
async function sendMessageToAI(message) {
try {
const response = await fetch(`${BASE_URL}chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_SECRET_KEY}`
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": message}
]
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.choices || !data.choices.length) {
throw new Error('Invalid response from AI service.');
}
return data;
} catch (error) {
console.error("Error fetching AI response:", error);
throw error;
}
}
function createChatWindow(isAutoAnswer) {
const chatWindowId = isAutoAnswer ? 'auto-answer-chat-window' : 'chat-window';
const chatWindow = document.createElement('div');
chatWindow.id = chatWindowId;
// 创建一个带有固定高度和滚动条的内容容器
const chatContentId = `${chatWindowId}-content`;
const chatContent = document.createElement('div');
chatContent.id = chatContentId;
chatContent.className = 'chat-content'; // 应用新的样式类
chatWindow.appendChild(chatContent);
if (isAutoAnswer) {
const instruction = document.createElement('div');
instruction.id = 'auto-answer-instruction';
instruction.textContent = '按下下方按钮,获取页面解答。(仅支持可被选中的文本)';
chatWindow.appendChild(instruction);
const generateAnswerButton = document.createElement('button');
generateAnswerButton.id = 'generate-answer-button';
generateAnswerButton.textContent = '点击生成页面解答';
generateAnswerButton.onclick = async function() {
const pageText = getPageText();
const prompt = `
提供一段包含问题及其选项的文本,请为每个问题进行编号,并组织成清晰的问题集。然后对每道题的每个选项进行详细解答,包括知识点分析、正确答案推导及错误选项解释。请使用中文整理并输出。
——
${pageText}
`;
const loadingMessage = document.createElement('div');
loadingMessage.textContent = '正在加载中…请耐心等待';
chatContent.innerHTML = ''; // 清空聊天记录
chatContent.appendChild(loadingMessage);
chatContent.scrollTop = chatContent.scrollHeight;
try {
const apiResponse = await sendMessageToAI(prompt);
const aiMessage = document.createElement('div');
aiMessage.innerHTML = '解答:
' + apiResponse.choices[0].message.content.replace(/\n/g, '
');
chatContent.appendChild(aiMessage);
chatContent.scrollTop = chatContent.scrollHeight;
const copyButton = document.createElement('div');
copyButton.className = 'copy-button';
copyButton.textContent = '点击复制此次回答';
copyButton.onclick = () => navigator.clipboard.writeText(apiResponse.choices[0].message.content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err));
chatContent.appendChild(copyButton);
chatContent.scrollTop = chatContent.scrollHeight;
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
} catch (error) {
const errorMessage = document.createElement('div');
errorMessage.textContent = '获取解答时出错,请稍后再试。';
chatContent.appendChild(errorMessage);
chatContent.scrollTop = chatContent.scrollHeight;
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
}
};
chatWindow.appendChild(generateAnswerButton);
} else {
// 对话GPT窗口逻辑
const inputContainer = document.createElement('div');
inputContainer.id = 'input-container';
const userInput = document.createElement('input');
userInput.type = 'text';
userInput.placeholder = '输入你的问题...';
inputContainer.appendChild(userInput);
const sendButton = document.createElement('button');
sendButton.textContent = '发送';
sendButton.onclick = async function() {
const userMessage = userInput.value.trim();
if (userMessage) {
userInput.value = '';
const loadingMessage = document.createElement('div');
loadingMessage.textContent = '正在加载中…请耐心等待';
chatContent.appendChild(loadingMessage);
chatContent.scrollTop = chatContent.scrollHeight;
try {
const apiResponse = await sendMessageToAI(userMessage);
const aiMessage = document.createElement('div');
aiMessage.innerHTML = '解答:
' + apiResponse.choices[0].message.content.replace(/\n/g, '
');
chatContent.appendChild(aiMessage);
chatContent.scrollTop = chatContent.scrollHeight;
const copyButton = document.createElement('div');
copyButton.className = 'copy-button';
copyButton.textContent = '点击复制此次回答';
copyButton.onclick = () => navigator.clipboard.writeText(apiResponse.choices[0].message.content).then(() => console.log('回答已复制到剪贴板')).catch(err => console.error('无法复制回答:', err));
chatContent.appendChild(copyButton);
chatContent.scrollTop = chatContent.scrollHeight;
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
} catch (error) {
const errorMessage = document.createElement('div');
errorMessage.textContent = '获取解答时出错,请稍后再试。';
chatContent.appendChild(errorMessage);
chatContent.scrollTop = chatContent.scrollHeight;
// 移除加载提示
setTimeout(() => chatContent.removeChild(loadingMessage), 0); // 确保先插入新内容再移除加载提示
}
}
};
inputContainer.appendChild(sendButton);
chatWindow.appendChild(inputContainer);
}
document.body.appendChild(chatWindow);
}
const buttonContainer = createButtonContainer();
document.body.insertBefore(buttonContainer, document.body.firstChild); // 插入到body的第一个子元素之前
const parentButton = createParentButton(buttonContainer);
let isExpanded = false;
parentButton.onclick = function(event) {
if (isExpanded) {
hideButtons();
} else {
showButtons();
}
isExpanded = !isExpanded;
};
function forceRedraw(element) {
element.style.zIndex = 1;
void element.offsetWidth;
element.style.zIndex = '';
}
function showButtons() {
const buttons = Array.from(buttonContainer.querySelectorAll('.option-button'));
buttons.forEach((button, index) => {
setTimeout(() => {
button.style.display = 'inline-block'; // 更改为 inline-block 以便在一行显示
button.style.opacity = '1';
forceRedraw(button);
}, index * 200);
});
}
function hideButtons() {
const buttons = Array.from(buttonContainer.querySelectorAll('.option-button')).reverse();
buttons.forEach((button, index) => {
setTimeout(() => {
button.style.opacity = '0';
setTimeout(() => {
button.style.display = 'none';
forceRedraw(button);
}, 200);
}, index * 200);
});
}
createOptionButtons(buttonContainer, 'chat-window');
createChatWindow(false); // 对话GPT窗口
createChatWindow(true); // 获取页面自动解答窗口
})();